package com.tensquare.article.service;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.servlet.http.HttpServletRequest;

import com.tensquare.article.client.NoticeClient;
import com.tensquare.article.client.UserClient;
import com.tensquare.article.dao.ArticleDao1;
import com.tensquare.article.dao.CommentDao;
import com.tensquare.article.pojo.Article1;
import com.tensquare.article.pojo.Comment;
import com.tensquare.notice.pojo.Notice;
import io.jsonwebtoken.Claims;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;
import util.IdWorker;

import com.tensquare.article.dao.ArticleDao;
import com.tensquare.article.pojo.Article;

/**
 * 文章的服务层，使用了redis缓存
 * 
 * @author Administrator
 *
 */
@Service
public class ArticleService {

	@Autowired
	private ArticleDao articleDao;
	@Autowired
	private ArticleDao1 articleDao1;
	@Autowired
	private IdWorker idWorker;
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private CommentDao commentDao;
	@Autowired
	private NoticeClient noticeClient;
	@Autowired
	private HttpServletRequest request;
	@Autowired
	private RabbitTemplate rabbitTemplate;
	@Autowired
	private UserClient userClient;

	/**
	 * 查询全部列表
	 * @return
	 */
	public List<Article> findAll() {
		return articleDao.findAll();
	}

	
	/**
	 * 条件查询+分页
	 * @param whereMap
	 * @param page
	 * @param size
	 * @return
	 */
	public Page<Article> findSearch(Map whereMap, int page, int size) {
		Specification<Article> specification = createSpecification(whereMap);
		PageRequest pageRequest =  PageRequest.of(page-1, size);
		return articleDao.findAll(specification, pageRequest);
	}

	
	/**
	 * 条件查询
	 * @param whereMap
	 * @return
	 */
	public List<Article> findSearch(Map whereMap) {
		Specification<Article> specification = createSpecification(whereMap);
		return articleDao.findAll(specification);
	}

	/**
	 * 根据ID查询实体
	 * @param id
	 * @return
	 */
	//@CachePut(value="article",key="#id")
	public Article findById(String id) {
		//从缓存中提取
		Article article= (Article)redisTemplate.opsForValue().get("article_"+id);
		// 如果缓存没有则到数据库查询并放入缓存
		if(article==null) {
			article = articleDao.findById(id).get();
            //设置缓存并设置过期时间，一小时
			redisTemplate.opsForValue().set("article_" + id, article,3600,TimeUnit.SECONDS);
		}
		return article;
	}

	/**
	 * 增加
	 * @param article
	 */
	@Transactional
	public String add(Article article) {
		article.setId( idWorker.nextId()+"" );
		//初始化数据
		article.setVisits(0); //浏览量
		article.setThumbup(0); //点赞数
		article.setComment(0); //评论数
		article.setState("0");
		articleDao.save(article);

		return article.getId();
	}

	/**
	 * 修改，需要先删除原来的缓存
	 * @param article
	 */
	@Transactional
	public void update(Article article) {
        redisTemplate.delete( "article_" + article.getId() );//删除缓存
		articleDao.save(article);
	}

	/**
	 * 删除，需要先删除原来的缓存
	 * @param id
	 */
	@Transactional
	public void deleteById(String id) {
        redisTemplate.delete( "article_" + id );//删除缓存
		articleDao.deleteById(id);
	}

	/**
	 * 动态条件构建
	 * @param searchMap
	 * @return
	 */
	private Specification<Article> createSpecification(Map searchMap) {

		return new Specification<Article>() {

			@Override
			public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				List<Predicate> predicateList = new ArrayList<Predicate>();
                // ID
                if (searchMap.get("id")!=null && !"".equals(searchMap.get("id"))) {
                	predicateList.add(cb.like(root.get("id").as(String.class), "%"+(String)searchMap.get("id")+"%"));
                }
                // 专栏ID
                if (searchMap.get("columnid")!=null && !"".equals(searchMap.get("columnid"))) {
                	predicateList.add(cb.like(root.get("columnid").as(String.class), "%"+(String)searchMap.get("columnid")+"%"));
                }
                // 用户ID
                if (searchMap.get("userid")!=null && !"".equals(searchMap.get("userid"))) {
                	predicateList.add(cb.like(root.get("userid").as(String.class), "%"+(String)searchMap.get("userid")+"%"));
                }
                // 标题
                if (searchMap.get("title")!=null && !"".equals(searchMap.get("title"))) {
                	predicateList.add(cb.like(root.get("title").as(String.class), "%"+(String)searchMap.get("title")+"%"));
                }
                // 文章正文
                if (searchMap.get("content")!=null && !"".equals(searchMap.get("content"))) {
                	predicateList.add(cb.like(root.get("content").as(String.class), "%"+(String)searchMap.get("content")+"%"));
                }
                // 文章封面
                if (searchMap.get("image")!=null && !"".equals(searchMap.get("image"))) {
                	predicateList.add(cb.like(root.get("image").as(String.class), "%"+(String)searchMap.get("image")+"%"));
                }
                // 是否公开
                if (searchMap.get("ispublic")!=null && !"".equals(searchMap.get("ispublic"))) {
                	predicateList.add(cb.like(root.get("ispublic").as(String.class), "%"+(String)searchMap.get("ispublic")+"%"));
                }
                // 是否置顶
                if (searchMap.get("istop")!=null && !"".equals(searchMap.get("istop"))) {
                	predicateList.add(cb.like(root.get("istop").as(String.class), "%"+(String)searchMap.get("istop")+"%"));
                }
                // 审核状态
                if (searchMap.get("state")!=null && !"".equals(searchMap.get("state"))) {
                	predicateList.add(cb.like(root.get("state").as(String.class), "%"+(String)searchMap.get("state")+"%"));
                }
                // 所属频道
                if (searchMap.get("channelid")!=null && !"".equals(searchMap.get("channelid"))) {
                	predicateList.add(cb.like(root.get("channelid").as(String.class), "%"+(String)searchMap.get("channelid")+"%"));
                }
                // URL
                if (searchMap.get("url")!=null && !"".equals(searchMap.get("url"))) {
                	predicateList.add(cb.like(root.get("url").as(String.class), "%"+(String)searchMap.get("url")+"%"));
                }
                // 类型
                if (searchMap.get("type")!=null && !"".equals(searchMap.get("type"))) {
                	predicateList.add(cb.like(root.get("type").as(String.class), "%"+(String)searchMap.get("type")+"%"));
                }
				
				return cb.and( predicateList.toArray(new Predicate[predicateList.size()]));

			}
		};

	}

	/**
	 * 文章审核
	 * @param id
	 */
	@Transactional
	public void examine(String id){
		articleDao.examine(id);

		Article article=articleDao.findById(id).get();

		//向RabbitMQ发送消息通知
		//使用jwt获取当前登录用户的userid，也就是文章作者的id
		//Claims claims=(Claims)request.getAttribute("user_claims");
		String authorId = article.getUserid();
		//获取需要通知的读者们的集合
		String authorKey = "article_author_" + authorId;
		Set<String> set = redisTemplate.boundSetOps(authorKey).members();
		//先保存到数据库
		for (String uid : set) {
			//消息通知
			Notice notice = new Notice();
			notice.setReceiverId(uid);
			notice.setOperatorId(authorId);
			notice.setAction("publish");		//发布新文章
			notice.setTargetType("article");	//操作对象：文章
			notice.setTargetId(article.getId());//文章id
			notice.setCreatetime(new Date());
			notice.setType("sys");				//系统类消息：群发
			notice.setState("0");				//未读
			noticeClient.add(notice);
		}
		//入库成功后，发送mq消息，内容是文章的id，指定交换机和路由键
		rabbitTemplate.convertAndSend("article_subscribe", authorId, article.getId());

	}

	/**
	 * 点赞
	 * @param id 文章ID
	 * @return
	 */
	@Transactional
	public int updateThumbup(String id){
		return articleDao.updateThumbup(id);
	}

	//根据频道id获取文章列表
    public Page<Article> getListByChannel(Map searchMap,String id, int page, int size) {
		searchMap.put("channelid",id);
		Specification<Article> specification = createSpecification(searchMap);
		PageRequest pageRequest =  PageRequest.of(page-1, size);
		return articleDao.findAll(specification,pageRequest);
    }
	//根据专栏id获取文章列表
	public Page<Article> getListByColumn(Map searchMap,String id, int page, int size) {
		searchMap.put("columnid",id);
		Specification<Article> specification = createSpecification(searchMap);
		PageRequest pageRequest =  PageRequest.of(page-1, size);
		return articleDao.findAll(specification,pageRequest);
	}

	//根据文章id设置对应标签
	@Transactional
	public void setArticleLabel(String aid,String labelid) {
		articleDao.setArticleLabel(aid,labelid);
	}

	//查询文章的评论列表
	public List<Comment> getArticleComments(String aid) {
		return commentDao.getArticleComments(aid);
	}
	//获取评论的评论列表
	public List<Comment> getCommentComments(String cid) {
		return commentDao.getCommentComments(cid);
	}

	//添加文章和评论的评论
	public void setComment(Comment comment){
		comment.setId(idWorker.nextId()+"" );
		commentDao.save(comment);
	}

	//根据文章id订阅作者
	//订阅的关系存放在redis中
	//建立消息队列和交换机的绑定关系
    public Boolean subscribe(String userId, String articleId) {
		//首先要根据文章id查询文章作者id
		String authorId = articleDao1.selectById(articleId).getUserid();

		//创建Rabbit管理器
		RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitTemplate.getConnectionFactory());
		//改进：1.定义RabbitMQ的direct类型的交换机
		DirectExchange exchange = new DirectExchange("article_subscribe");
		rabbitAdmin.declareExchange(exchange);		//声明交换机

		//改进：2.定义用户的RabbitMQ队列创建读者的消息队列，true表示是否持久化
		Queue queue=new Queue("article_subscribe_" + userId, true);

		//改进：3.将队列通过路由键绑定或解绑direct交换机，一个作者的路由键对应多个读者的队列
		//声明exchange和queue的绑定关系，设置路由键为作者id，队列，交换机，路由键
		//这里只是声明，并没有真正绑定？？？
		Binding binding = BindingBuilder.bind(queue).to(exchange).with(authorId);

		String userKey = "article_subscribe_" + userId;		//存放用户订阅作者的集合
		String authorKey = "article_author_" + authorId;	//存放作者的订阅者集合
		//设置redis中的key以字符串的方式显现
		//redisTemplate.setKeySerializer(new StringRedisSerializer());

		//把这两个订阅和被订阅的关系存放在redis中，查询该用户是否已经订阅该作者
		Boolean flag = redisTemplate.boundSetOps(userKey).isMember(authorId);
		if (flag) {
			//如果为flag为true，已经订阅,则取消订阅，并且删除队列绑定
			redisTemplate.boundSetOps(userKey).remove(authorId);//userKey对应多个authorId，表示关注了多个作者
			redisTemplate.boundSetOps(authorKey).remove(userId);//authorKey对应了多个userId，表示作者有多个粉丝
			//删除绑定
			rabbitAdmin.removeBinding(binding);

			//删除数据库建立的通知消息提醒
			Notice notice = new Notice();
			notice.setReceiverId(authorId);
			notice.setOperatorId(userId);
			notice.setAction("subscribe");
			noticeClient.delete(notice);

			//更新对方的粉丝数
			userClient.incFanscount(authorId,-1);
			//更新我的关注数
			userClient.incFollowcount(userId,-1);

			return false;
		} else {
			// 如果为flag为false，没有订阅，则进行订阅，先设置redis中的订阅双重关系
			redisTemplate.boundSetOps(userKey).add(authorId);
			redisTemplate.boundSetOps(authorKey).add(userId);
			//声明队列和绑定队列
			rabbitAdmin.declareQueue(queue);
			rabbitAdmin.declareBinding(binding);		//这里才是真正的绑定

			//将这条通知添加到数据库
			Notice notice = new Notice();
			notice.setReceiverId(authorId);
			notice.setOperatorId(userId);
			notice.setAction("subscribe");	//新订阅
			notice.setTargetType("user");	//操作对象：用户
			notice.setTargetId(userId);
			notice.setCreatetime(new Date());
			notice.setType("user");			//用户通知
			notice.setState("0");			//未读
			noticeClient.add(notice);		//保存通知消息

			//更新对方的粉丝数
			userClient.incFanscount(authorId,1);
			//更新我的关注数
			userClient.incFollowcount(userId,1);

			return true;
		}
    }

    //文章点赞
	public void thumbup(String articleId,String userid) {
		Article1 article = articleDao1.selectById(articleId);
		article.setThumbup(article.getThumbup() + 1);
		articleDao1.updateById(article);

		//增加点赞的消息通知，把点赞成功消息发送给作者（点对点消息）
		Notice notice = new Notice();
		notice.setReceiverId(article.getUserid());
		notice.setOperatorId(userid);
		notice.setAction("thumbup");
		notice.setTargetType("article");
		notice.setTargetId(articleId);
		notice.setCreatetime(new Date());
		notice.setType("user");		//点对点的通知类型
		notice.setState("0");
		noticeClient.add(notice);	//保存通知消息

		//1 创建Rabbit管理器
		RabbitAdmin rabbitAdmin = new RabbitAdmin(rabbitTemplate.getConnectionFactory());
		//2 创建队列，每个用户都有自己的队列，通过用户id进行区分
		Queue queue = new Queue("article_thumbup_" + article.getUserid(), true);//我收到了点赞消息
		rabbitAdmin.declareQueue(queue);
		//3 发送消息
		rabbitTemplate.convertAndSend("article_thumbup_"+article.getUserid(),articleId);
	}

	//查询作者其他文章列表
    public  List<String> getOtherArticles(String uid, String aid) {
		return articleDao.getOtherArticles(uid,aid);
    }

    //查询热门文章列表
	public List<String> getHotArticles() {
		return articleDao.getHotArticles();
	}

	//根据评论id查询评论
	public Comment getCommentById(String id) {
		return commentDao.findById(id).get();
	}

	//获取我的文章列表，分页
    public Page<Article> getMyArticles(String userId, int page, int size) {
		PageRequest pageRequest =  PageRequest.of(page-1, size);
		return articleDao.getMyArticles(userId,pageRequest);
    }
}
